%{
/*
**  Originally written by Steven M. Bellovin <smb@research.att.com> while
**  at the University of North Carolina at Chapel Hill.  Later tweaked by
**  a couple of people on Usenet.  Completely overhauled by Rich $alz
**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
**  send any email to Rich.
**
**  This grammar has 10 shift/reduce conflicts.
**
**  This code is in the public domain and has no copyright.
*/
/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
/* SUPPRESS 288 on yyerrlab *//* Label unused */

#ifdef HAVE_CONFIG_H
#include <config.h>

#ifdef FORCE_ALLOCA_H
#include <alloca.h>
#endif
#endif

/* Since the code of getdate.y is not included in the Emacs executable
   itself, there is no need to #define static in this file.  Even if
   the code were included in the Emacs executable, it probably
   wouldn't do any harm to #undef it here; this will only cause
   problems if we try to write to a static variable, which I don't
   think this code needs to do.  */
#ifdef emacs
#undef static
#endif

#include <stdio.h>
#include <ctype.h>

#if defined (STDC_HEADERS) || !defined (isascii)
# define ISASCII(c) 1
#else
# define ISASCII(c) isascii(c)
#endif

#define ISSPACE(c) (ISASCII (c) && isspace (c))
#define ISALPHA(c) (ISASCII (c) && isalpha (c))
#define ISUPPER(c) (ISASCII (c) && isupper (c))
#define ISDIGIT(c) (ISASCII (c) && isdigit (c))

#if	defined (vms)
#include <types.h>
#include <time.h>
#else
#include <sys/types.h>
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#ifdef timezone
#undef timezone /* needed for sgi */
#endif

#if defined (HAVE_SYS_TIMEB_H)
#include <sys/timeb.h>
#else

/* get_date uses the obsolete `struct timeb' in its interface!  FIXME.
   Since some systems don't have it, we define it here;
   callers must do likewise.  */
struct timeb
  {
    time_t		time;		/* Seconds since the epoch	*/
    unsigned short	millitm;	/* Field not used		*/
    short		timezone;	/* Minutes west of GMT		*/
    short		dstflag;	/* Field not used		*/
};
#endif /* defined (HAVE_SYS_TIMEB_H) */

#endif	/* defined (vms) */

#if defined (STDC_HEADERS) || defined (USG)
#include <string.h>
#endif

/* Some old versions of bison generate parsers that use bcopy.
   That loses on systems that don't provide the function, so we have
   to redefine it here.  */
#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
#define bcopy(from, to, len) memcpy ((to), (from), (len))
#endif

extern struct tm	*gmtime ();
extern struct tm	*localtime ();

/* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
   as well as gratuitiously global symbol names, so we can have multiple
   yacc generated parsers in the same program.  Note that these are only
   the variables produced by yacc.  If other parser generators (bison,
   byacc, etc) produce additional global names that conflict at link time,
   then those parser generators need to be fixed instead of adding those
   names to this list. */

#define yymaxdepth gd_maxdepth
#define yyparse gd_parse
#define yylex   gd_lex
#define yyerror gd_error
#define yylval  gd_lval
#define yychar  gd_char
#define yydebug gd_debug
#define yypact  gd_pact
#define yyr1    gd_r1
#define yyr2    gd_r2
#define yydef   gd_def
#define yychk   gd_chk
#define yypgo   gd_pgo
#define yyact   gd_act
#define yyexca  gd_exca
#define yyerrflag gd_errflag
#define yynerrs gd_nerrs
#define yyps    gd_ps
#define yypv    gd_pv
#define yys     gd_s
#define yy_yys  gd_yys
#define yystate gd_state
#define yytmp   gd_tmp
#define yyv     gd_v
#define yy_yyv  gd_yyv
#define yyval   gd_val
#define yylloc  gd_lloc
#define yyreds  gd_reds          /* With YYDEBUG defined */
#define yytoks  gd_toks          /* With YYDEBUG defined */
#define yylhs   gd_yylhs
#define yylen   gd_yylen
#define yydefred gd_yydefred
#define yydgoto gd_yydgoto
#define yysindex gd_yysindex
#define yyrindex gd_yyrindex
#define yygindex gd_yygindex
#define yytable  gd_yytable
#define yycheck  gd_yycheck

static int yylex ();
static int yyerror ();

#define EPOCH		1970
#define DOOMSDAY	2038
#define HOUR(x)		((time_t)(x) * 60)
#define SECSPERDAY	(24L * 60L * 60L)

#define MAX_BUFF_LEN    128   /* size of buffer to read the date into */

/*
**  An entry in the lexical lookup table.
*/
typedef struct _TABLE {
    const char	*name;
    int		type;
    time_t	value;
} TABLE;


/*
**  Daylight-savings mode:  on, off, or not yet known.
*/
typedef enum _DSTMODE {
    DSTon, DSToff, DSTmaybe
} DSTMODE;

/*
**  Meridian:  am, pm, or 24-hour style.
*/
typedef enum _MERIDIAN {
    MERam, MERpm, MER24
} MERIDIAN;


/*
**  Global variables.  We could get rid of most of these by using a good
**  union as the yacc stack.  (This routine was originally written before
**  yacc had the %union construct.)  Maybe someday; right now we only use
**  the %union very rarely.
*/
static char	*yyInput;
static DSTMODE	yyDSTmode;
static time_t	yyDayOrdinal;
static time_t	yyDayNumber;
static int	yyHaveDate;
static int	yyHaveDay;
static int	yyHaveRel;
static int	yyHaveTime;
static int	yyHaveZone;
static time_t	yyTimezone;
static time_t	yyDay;
static time_t	yyHour;
static time_t	yyMinutes;
static time_t	yyMonth;
static time_t	yySeconds;
static time_t	yyYear;
static MERIDIAN	yyMeridian;
static time_t	yyRelMonth;
static time_t	yyRelSeconds;

%}

%union {
    time_t		Number;
    enum _MERIDIAN	Meridian;
}

%token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
%token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST

%type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
%type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE
%type	<Meridian>	tMERIDIAN o_merid

%%

spec	: /* NULL */
	| spec item
	;

item	: time {
	    yyHaveTime++;
	}
	| zone {
	    yyHaveZone++;
	}
	| date {
	    yyHaveDate++;
	}
	| day {
	    yyHaveDay++;
	}
	| rel {
	    yyHaveRel++;
	}
	| number
	;

time	: tUNUMBER tMERIDIAN {
	    yyHour = $1;
	    yyMinutes = 0;
	    yySeconds = 0;
	    yyMeridian = $2;
	}
	| tUNUMBER ':' tUNUMBER o_merid {
	    yyHour = $1;
	    yyMinutes = $3;
	    yySeconds = 0;
	    yyMeridian = $4;
	}
	| tUNUMBER ':' tUNUMBER tSNUMBER {
	    yyHour = $1;
	    yyMinutes = $3;
	    yyMeridian = MER24;
	    yyDSTmode = DSToff;
	    yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
	}
	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
	    yyHour = $1;
	    yyMinutes = $3;
	    yySeconds = $5;
	    yyMeridian = $6;
	}
	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
	    yyHour = $1;
	    yyMinutes = $3;
	    yySeconds = $5;
	    yyMeridian = MER24;
	    yyDSTmode = DSToff;
	    yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
	}
	;

zone	: tZONE {
	    yyTimezone = $1;
	    yyDSTmode = DSToff;
	}
	| tDAYZONE {
	    yyTimezone = $1;
	    yyDSTmode = DSTon;
	}
	|
	  tZONE tDST {
	    yyTimezone = $1;
	    yyDSTmode = DSTon;
	}
	;

day	: tDAY {
	    yyDayOrdinal = 1;
	    yyDayNumber = $1;
	}
	| tDAY ',' {
	    yyDayOrdinal = 1;
	    yyDayNumber = $1;
	}
	| tUNUMBER tDAY {
	    yyDayOrdinal = $1;
	    yyDayNumber = $2;
	}
	;

date	: tUNUMBER '/' tUNUMBER {
	    yyMonth = $1;
	    yyDay = $3;
	}
	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
	  /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
	     The goal in recognizing YYYY/MM/DD is solely to support legacy
	     machine-generated dates like those in an RCS log listing.  If
	     you want portability, use the ISO 8601 format.  */
	  if ($1 >= 1000)
	    {
	      yyYear = $1;
	      yyMonth = $3;
	      yyDay = $5;
	    }
	  else
	    {
	      yyMonth = $1;
	      yyDay = $3;
	      yyYear = $5;
	    }
	}
	| tUNUMBER tSNUMBER tSNUMBER {
	    /* ISO 8601 format.  yyyy-mm-dd.  */
	    yyYear = $1;
	    yyMonth = -$2;
	    yyDay = -$3;
	}
	| tUNUMBER tMONTH tSNUMBER {
	    /* e.g. 17-JUN-1992.  */
	    yyDay = $1;
	    yyMonth = $2;
	    yyYear = -$3;
	}
	| tMONTH tUNUMBER {
	    yyMonth = $1;
	    yyDay = $2;
	}
	| tMONTH tUNUMBER ',' tUNUMBER {
	    yyMonth = $1;
	    yyDay = $2;
	    yyYear = $4;
	}
	| tUNUMBER tMONTH {
	    yyMonth = $2;
	    yyDay = $1;
	}
	| tUNUMBER tMONTH tUNUMBER {
	    yyMonth = $2;
	    yyDay = $1;
	    yyYear = $3;
	}
	;

rel	: relunit tAGO {
	    yyRelSeconds = -yyRelSeconds;
	    yyRelMonth = -yyRelMonth;
	}
	| relunit
	;

relunit	: tUNUMBER tMINUTE_UNIT {
	    yyRelSeconds += $1 * $2 * 60L;
	}
	| tSNUMBER tMINUTE_UNIT {
	    yyRelSeconds += $1 * $2 * 60L;
	}
	| tMINUTE_UNIT {
	    yyRelSeconds += $1 * 60L;
	}
	| tSNUMBER tSEC_UNIT {
	    yyRelSeconds += $1;
	}
	| tUNUMBER tSEC_UNIT {
	    yyRelSeconds += $1;
	}
	| tSEC_UNIT {
	    yyRelSeconds++;
	}
	| tSNUMBER tMONTH_UNIT {
	    yyRelMonth += $1 * $2;
	}
	| tUNUMBER tMONTH_UNIT {
	    yyRelMonth += $1 * $2;
	}
	| tMONTH_UNIT {
	    yyRelMonth += $1;
	}
	;

number	: tUNUMBER {
	    if (yyHaveTime && yyHaveDate && !yyHaveRel)
		yyYear = $1;
	    else {
		if ($1>10000) {
		    yyHaveDate++;
		    yyDay= ($1)%100;
		    yyMonth= ($1/100)%100;
		    yyYear = $1/10000;
		}
		else {
		    yyHaveTime++;
		    if ($1 < 100) {
			yyHour = $1;
			yyMinutes = 0;
		    }
		    else {
		    	yyHour = $1 / 100;
		    	yyMinutes = $1 % 100;
		    }
		    yySeconds = 0;
		    yyMeridian = MER24;
	        }
	    }
	}
	;

o_merid	: /* NULL */ {
	    $$ = MER24;
	}
	| tMERIDIAN {
	    $$ = $1;
	}
	;

%%

/* Month and day table. */
static TABLE const MonthDayTable[] = {
    { "january",	tMONTH,  1 },
    { "february",	tMONTH,  2 },
    { "march",		tMONTH,  3 },
    { "april",		tMONTH,  4 },
    { "may",		tMONTH,  5 },
    { "june",		tMONTH,  6 },
    { "july",		tMONTH,  7 },
    { "august",		tMONTH,  8 },
    { "september",	tMONTH,  9 },
    { "sept",		tMONTH,  9 },
    { "october",	tMONTH, 10 },
    { "november",	tMONTH, 11 },
    { "december",	tMONTH, 12 },
    { "sunday",		tDAY, 0 },
    { "monday",		tDAY, 1 },
    { "tuesday",	tDAY, 2 },
    { "tues",		tDAY, 2 },
    { "wednesday",	tDAY, 3 },
    { "wednes",		tDAY, 3 },
    { "thursday",	tDAY, 4 },
    { "thur",		tDAY, 4 },
    { "thurs",		tDAY, 4 },
    { "friday",		tDAY, 5 },
    { "saturday",	tDAY, 6 },
    { NULL }
};

/* Time units table. */
static TABLE const UnitsTable[] = {
    { "year",		tMONTH_UNIT,	12 },
    { "month",		tMONTH_UNIT,	1 },
    { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
    { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
    { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
    { "hour",		tMINUTE_UNIT,	60 },
    { "minute",		tMINUTE_UNIT,	1 },
    { "min",		tMINUTE_UNIT,	1 },
    { "second",		tSEC_UNIT,	1 },
    { "sec",		tSEC_UNIT,	1 },
    { NULL }
};

/* Assorted relative-time words. */
static TABLE const OtherTable[] = {
    { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
    { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
    { "today",		tMINUTE_UNIT,	0 },
    { "now",		tMINUTE_UNIT,	0 },
    { "last",		tUNUMBER,	-1 },
    { "this",		tMINUTE_UNIT,	0 },
    { "next",		tUNUMBER,	2 },
    { "first",		tUNUMBER,	1 },
/*  { "second",		tUNUMBER,	2 }, */
    { "third",		tUNUMBER,	3 },
    { "fourth",		tUNUMBER,	4 },
    { "fifth",		tUNUMBER,	5 },
    { "sixth",		tUNUMBER,	6 },
    { "seventh",	tUNUMBER,	7 },
    { "eighth",		tUNUMBER,	8 },
    { "ninth",		tUNUMBER,	9 },
    { "tenth",		tUNUMBER,	10 },
    { "eleventh",	tUNUMBER,	11 },
    { "twelfth",	tUNUMBER,	12 },
    { "ago",		tAGO,	1 },
    { NULL }
};

/* The timezone table. */
/* Some of these are commented out because a time_t can't store a float. */
static TABLE const TimezoneTable[] = {
    { "gmt",	tZONE,     HOUR ( 0) },	/* Greenwich Mean */
    { "ut",	tZONE,     HOUR ( 0) },	/* Universal (Coordinated) */
    { "utc",	tZONE,     HOUR ( 0) },
    { "wet",	tZONE,     HOUR ( 0) },	/* Western European */
    { "bst",	tDAYZONE,  HOUR ( 0) },	/* British Summer */
    { "wat",	tZONE,     HOUR ( 1) },	/* West Africa */
    { "at",	tZONE,     HOUR ( 2) },	/* Azores */
#if	0
    /* For completeness.  BST is also British Summer, and GST is
     * also Guam Standard. */
    { "bst",	tZONE,     HOUR ( 3) },	/* Brazil Standard */
    { "gst",	tZONE,     HOUR ( 3) },	/* Greenland Standard */
#endif
#if 0
    { "nft",	tZONE,     HOUR (3.5) },	/* Newfoundland */
    { "nst",	tZONE,     HOUR (3.5) },	/* Newfoundland Standard */
    { "ndt",	tDAYZONE,  HOUR (3.5) },	/* Newfoundland Daylight */
#endif
    { "ast",	tZONE,     HOUR ( 4) },	/* Atlantic Standard */
    { "adt",	tDAYZONE,  HOUR ( 4) },	/* Atlantic Daylight */
    { "est",	tZONE,     HOUR ( 5) },	/* Eastern Standard */
    { "edt",	tDAYZONE,  HOUR ( 5) },	/* Eastern Daylight */
    { "cst",	tZONE,     HOUR ( 6) },	/* Central Standard */
    { "cdt",	tDAYZONE,  HOUR ( 6) },	/* Central Daylight */
    { "mst",	tZONE,     HOUR ( 7) },	/* Mountain Standard */
    { "mdt",	tDAYZONE,  HOUR ( 7) },	/* Mountain Daylight */
    { "pst",	tZONE,     HOUR ( 8) },	/* Pacific Standard */
    { "pdt",	tDAYZONE,  HOUR ( 8) },	/* Pacific Daylight */
    { "yst",	tZONE,     HOUR ( 9) },	/* Yukon Standard */
    { "ydt",	tDAYZONE,  HOUR ( 9) },	/* Yukon Daylight */
    { "hst",	tZONE,     HOUR (10) },	/* Hawaii Standard */
    { "hdt",	tDAYZONE,  HOUR (10) },	/* Hawaii Daylight */
    { "cat",	tZONE,     HOUR (10) },	/* Central Alaska */
    { "ahst",	tZONE,     HOUR (10) },	/* Alaska-Hawaii Standard */
    { "nt",	tZONE,     HOUR (11) },	/* Nome */
    { "idlw",	tZONE,     HOUR (12) },	/* International Date Line West */
    { "cet",	tZONE,     -HOUR (1) },	/* Central European */
    { "met",	tZONE,     -HOUR (1) },	/* Middle European */
    { "mewt",	tZONE,     -HOUR (1) },	/* Middle European Winter */
    { "mest",	tDAYZONE,  -HOUR (1) },	/* Middle European Summer */
    { "mesz",	tDAYZONE,  -HOUR (1) },	/* Middle European Summer */
    { "swt",	tZONE,     -HOUR (1) },	/* Swedish Winter */
    { "sst",	tDAYZONE,  -HOUR (1) },	/* Swedish Summer */
    { "fwt",	tZONE,     -HOUR (1) },	/* French Winter */
    { "fst",	tDAYZONE,  -HOUR (1) },	/* French Summer */
    { "eet",	tZONE,     -HOUR (2) },	/* Eastern Europe, USSR Zone 1 */
    { "bt",	tZONE,     -HOUR (3) },	/* Baghdad, USSR Zone 2 */
#if 0
    { "it",	tZONE,     -HOUR (3.5) },/* Iran */
#endif
    { "zp4",	tZONE,     -HOUR (4) },	/* USSR Zone 3 */
    { "zp5",	tZONE,     -HOUR (5) },	/* USSR Zone 4 */
#if 0
    { "ist",	tZONE,     -HOUR (5.5) },/* Indian Standard */
#endif
    { "zp6",	tZONE,     -HOUR (6) },	/* USSR Zone 5 */
#if	0
    /* For completeness.  NST is also Newfoundland Standard, and SST is
     * also Swedish Summer. */
    { "nst",	tZONE,     -HOUR (6.5) },/* North Sumatra */
    { "sst",	tZONE,     -HOUR (7) },	/* South Sumatra, USSR Zone 6 */
#endif	/* 0 */
    { "wast",	tZONE,     -HOUR (7) },	/* West Australian Standard */
    { "wadt",	tDAYZONE,  -HOUR (7) },	/* West Australian Daylight */
#if 0
    { "jt",	tZONE,     -HOUR (7.5) },/* Java (3pm in Cronusland!) */
#endif
    { "cct",	tZONE,     -HOUR (8) },	/* China Coast, USSR Zone 7 */
    { "jst",	tZONE,     -HOUR (9) },	/* Japan Standard, USSR Zone 8 */
#if 0
    { "cast",	tZONE,     -HOUR (9.5) },/* Central Australian Standard */
    { "cadt",	tDAYZONE,  -HOUR (9.5) },/* Central Australian Daylight */
#endif
    { "east",	tZONE,     -HOUR (10) },	/* Eastern Australian Standard */
    { "eadt",	tDAYZONE,  -HOUR (10) },	/* Eastern Australian Daylight */
    { "gst",	tZONE,     -HOUR (10) },	/* Guam Standard, USSR Zone 9 */
    { "nzt",	tZONE,     -HOUR (12) },	/* New Zealand */
    { "nzst",	tZONE,     -HOUR (12) },	/* New Zealand Standard */
    { "nzdt",	tDAYZONE,  -HOUR (12) },	/* New Zealand Daylight */
    { "idle",	tZONE,     -HOUR (12) },	/* International Date Line East */
    {  NULL  }
};

/* Military timezone table. */
static TABLE const MilitaryTable[] = {
    { "a",	tZONE,	HOUR (  1) },
    { "b",	tZONE,	HOUR (  2) },
    { "c",	tZONE,	HOUR (  3) },
    { "d",	tZONE,	HOUR (  4) },
    { "e",	tZONE,	HOUR (  5) },
    { "f",	tZONE,	HOUR (  6) },
    { "g",	tZONE,	HOUR (  7) },
    { "h",	tZONE,	HOUR (  8) },
    { "i",	tZONE,	HOUR (  9) },
    { "k",	tZONE,	HOUR ( 10) },
    { "l",	tZONE,	HOUR ( 11) },
    { "m",	tZONE,	HOUR ( 12) },
    { "n",	tZONE,	HOUR (- 1) },
    { "o",	tZONE,	HOUR (- 2) },
    { "p",	tZONE,	HOUR (- 3) },
    { "q",	tZONE,	HOUR (- 4) },
    { "r",	tZONE,	HOUR (- 5) },
    { "s",	tZONE,	HOUR (- 6) },
    { "t",	tZONE,	HOUR (- 7) },
    { "u",	tZONE,	HOUR (- 8) },
    { "v",	tZONE,	HOUR (- 9) },
    { "w",	tZONE,	HOUR (-10) },
    { "x",	tZONE,	HOUR (-11) },
    { "y",	tZONE,	HOUR (-12) },
    { "z",	tZONE,	HOUR (  0) },
    { NULL }
};




/* ARGSUSED */
static int
yyerror (s)
    char	*s;
{
  return 0;
}


static time_t
ToSeconds (Hours, Minutes, Seconds, Meridian)
    time_t	Hours;
    time_t	Minutes;
    time_t	Seconds;
    MERIDIAN	Meridian;
{
  if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
    return -1;
  switch (Meridian) {
  case MER24:
    if (Hours < 0 || Hours > 23)
      return -1;
    return (Hours * 60L + Minutes) * 60L + Seconds;
  case MERam:
    if (Hours < 1 || Hours > 12)
      return -1;
    if (Hours == 12)
      Hours = 0;
    return (Hours * 60L + Minutes) * 60L + Seconds;
  case MERpm:
    if (Hours < 1 || Hours > 12)
      return -1;
    if (Hours == 12)
      Hours = 0;
    return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
  default:
    abort ();
  }
  /* NOTREACHED */
}


static time_t
Convert (Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
    time_t	Month;
    time_t	Day;
    time_t	Year;
    time_t	Hours;
    time_t	Minutes;
    time_t	Seconds;
    MERIDIAN	Meridian;
    DSTMODE	DSTmode;
{
  static int DaysInMonth[12] = {
    31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  };
  time_t	tod;
  time_t	Julian;
  int		i;

  if (Year < 0)
    Year = -Year;
  if (Year < DOOMSDAY-2000)
    Year += 2000;
  else if (Year < 100)
    Year += 1900;
  DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
    ? 29 : 28;
  if (Year < EPOCH || Year > DOOMSDAY
      || Month < 1 || Month > 12
      /* Lint fluff:  "conversion from long may lose accuracy" */
      || Day < 1 || Day > DaysInMonth[(int)--Month])
    return -1;

  for (Julian = Day - 1, i = 0; i < Month; i++)
    Julian += DaysInMonth[i];
  for (i = EPOCH; i < Year; i++)
    Julian += 365 + (i % 4 == 0);
  Julian *= SECSPERDAY;
  Julian += yyTimezone * 60L;
  if ((tod = ToSeconds (Hours, Minutes, Seconds, Meridian)) < 0)
    return -1;
  Julian += tod;
  if (DSTmode == DSTon
      || (DSTmode == DSTmaybe && localtime (&Julian)->tm_isdst))
    Julian -= 60 * 60;
  return Julian;
}


static time_t
DSTcorrect (Start, Future)
    time_t	Start;
    time_t	Future;
{
  time_t	StartDay;
  time_t	FutureDay;

  StartDay = (localtime (&Start)->tm_hour + 1) % 24;
  FutureDay = (localtime (&Future)->tm_hour + 1) % 24;
  return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
}


static time_t
RelativeDate (Start, DayOrdinal, DayNumber)
    time_t	Start;
    time_t	DayOrdinal;
    time_t	DayNumber;
{
  struct tm	*tm;
  time_t	now;

  now = Start;
  tm = localtime (&now);
  now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
  now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
  return DSTcorrect (Start, now);
}


static time_t
RelativeMonth (Start, RelMonth)
    time_t	Start;
    time_t	RelMonth;
{
  struct tm	*tm;
  time_t	Month;
  time_t	Year;

  if (RelMonth == 0)
    return 0;
  tm = localtime (&Start);
  Month = 12 * (1900 + tm->tm_year) + tm->tm_mon + RelMonth;
  Year = Month / 12;
  Month = Month % 12 + 1;
  return DSTcorrect (Start,
		     Convert (Month, (time_t)tm->tm_mday, Year,
			      (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
			      MER24, DSTmaybe));
}


static int
LookupWord (buff)
    char		*buff;
{
  register char	*p;
  register char	*q;
  register const TABLE	*tp;
  int			i;
  int			abbrev;

  /* Make it lowercase. */
  for (p = buff; *p; p++)
    if (ISUPPER (*p))
      *p = tolower (*p);

  if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0) {
    yylval.Meridian = MERam;
    return tMERIDIAN;
  }
  if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0) {
    yylval.Meridian = MERpm;
    return tMERIDIAN;
  }

  /* See if we have an abbreviation for a month. */
  if (strlen (buff) == 3)
    abbrev = 1;
  else if (strlen (buff) == 4 && buff[3] == '.') {
    abbrev = 1;
    buff[3] = '\0';
  }
  else
    abbrev = 0;

  for (tp = MonthDayTable; tp->name; tp++) {
    if (abbrev) {
      if (strncmp (buff, tp->name, 3) == 0) {
	yylval.Number = tp->value;
	return tp->type;
      }
    }
    else if (strcmp (buff, tp->name) == 0) {
      yylval.Number = tp->value;
      return tp->type;
    }
  }

  for (tp = TimezoneTable; tp->name; tp++)
    if (strcmp (buff, tp->name) == 0) {
      yylval.Number = tp->value;
      return tp->type;
    }

  if (strcmp (buff, "dst") == 0)
    return tDST;

  for (tp = UnitsTable; tp->name; tp++)
    if (strcmp (buff, tp->name) == 0) {
      yylval.Number = tp->value;
      return tp->type;
    }

  /* Strip off any plural and try the units table again. */
  i = strlen (buff) - 1;
  if (buff[i] == 's') {
    buff[i] = '\0';
    for (tp = UnitsTable; tp->name; tp++)
      if (strcmp (buff, tp->name) == 0) {
	yylval.Number = tp->value;
	return tp->type;
      }
    buff[i] = 's';		/* Put back for "this" in OtherTable. */
  }

  for (tp = OtherTable; tp->name; tp++)
    if (strcmp (buff, tp->name) == 0) {
      yylval.Number = tp->value;
      return tp->type;
    }

  /* Military timezones. */
  if (buff[1] == '\0' && ISALPHA (*buff)) {
    for (tp = MilitaryTable; tp->name; tp++)
      if (strcmp (buff, tp->name) == 0) {
	yylval.Number = tp->value;
	return tp->type;
      }
  }

  /* Drop out any periods and try the timezone table again. */
  for (i = 0, p = q = buff; *q; q++)
    if (*q != '.')
      *p++ = *q;
    else
      i++;
  *p = '\0';
  if (i)
    for (tp = TimezoneTable; tp->name; tp++)
      if (strcmp (buff, tp->name) == 0) {
	yylval.Number = tp->value;
	return tp->type;
      }

  return tID;
}


static int
yylex ()
{
  register char	c;
  register char	*p;
  char		buff[20];
  int			Count;
  int			sign;

  for ( ; ; ) {
    while (ISSPACE (*yyInput))
      yyInput++;

    if (ISDIGIT (c = *yyInput) || c == '-' || c == '+') {
      if (c == '-' || c == '+') {
	sign = c == '-' ? -1 : 1;
	if (!ISDIGIT (*++yyInput))
	  /* skip the '-' sign */
	  continue;
      }
      else
	sign = 0;
      for (yylval.Number = 0; ISDIGIT (c = *yyInput++); )
	yylval.Number = 10 * yylval.Number + c - '0';
      yyInput--;
      if (sign < 0)
	yylval.Number = -yylval.Number;
      return sign ? tSNUMBER : tUNUMBER;
    }
    if (ISALPHA (c)) {
      for (p = buff; ISALPHA (c = *yyInput++) || c == '.'; )
	if (p < &buff[sizeof buff - 1])
	  *p++ = c;
      *p = '\0';
      yyInput--;
      return LookupWord (buff);
    }
    if (c != '(')
      return *yyInput++;
    Count = 0;
    do {
      c = *yyInput++;
      if (c == '\0')
	return c;
      if (c == '(')
	Count++;
      else if (c == ')')
	Count--;
    } while (Count > 0);
  }
}

#define TM_YEAR_ORIGIN 1900

/* Yield A - B, measured in seconds.  */
static long
difftm (a, b)
     struct tm *a, *b;
{
  int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
  int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
  long days = (
	       /* difference in day of year */
	       a->tm_yday - b->tm_yday
	       /* + intervening leap days */
	       +  ((ay >> 2) - (by >> 2))
	       -  (ay/100 - by/100)
	       +  ((ay/100 >> 2) - (by/100 >> 2))
	       /* + difference in years * 365 */
	       +  (long)(ay-by) * 365
	       );
  return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
	      + (a->tm_min - b->tm_min))
	  + (a->tm_sec - b->tm_sec));
}

time_t
get_date (p, now)
    char		*p;
    struct timeb	*now;
{
  struct tm		*tm, gmt;
  struct timeb	ftz;
  time_t		Start;
  time_t		tod;

  yyInput = p;
  if (now == NULL) {
    now = &ftz;
    (void)time (&ftz.time);

    if (! (tm = gmtime (&ftz.time)))
      return -1;
    gmt = *tm;			/* Make a copy, in case localtime modifies *tm.  */

    if (! (tm = localtime (&ftz.time)))
      return -1;

    ftz.timezone = difftm (&gmt, tm) / 60;
    if (tm->tm_isdst)
      ftz.timezone += 60;
  }

  tm = localtime (&now->time);
  yyYear = tm->tm_year;
  yyMonth = tm->tm_mon + 1;
  yyDay = tm->tm_mday;
  yyTimezone = now->timezone;
  yyDSTmode = DSTmaybe;
  yyHour = 0;
  yyMinutes = 0;
  yySeconds = 0;
  yyMeridian = MER24;
  yyRelSeconds = 0;
  yyRelMonth = 0;
  yyHaveDate = 0;
  yyHaveDay = 0;
  yyHaveRel = 0;
  yyHaveTime = 0;
  yyHaveZone = 0;

  if (yyparse ()
      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
    return -1;

  if (yyHaveDate || yyHaveTime || yyHaveDay) {
    Start = Convert (yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
		     yyMeridian, yyDSTmode);
    if (Start < 0)
      return -1;
  }
  else {
    Start = now->time;
    if (!yyHaveRel)
      Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
  }

  Start += yyRelSeconds;
  Start += RelativeMonth (Start, yyRelMonth);

  if (yyHaveDay && !yyHaveDate) {
    tod = RelativeDate (Start, yyDayOrdinal, yyDayNumber);
    Start += tod;
  }

  /* Have to do *something* with a legitimate -1 so it's distinguishable
   * from the error return value.  (Alternately could set errno on error.) */
  return Start == -1 ? 0 : Start;
}


#if	defined (TEST)

/* ARGSUSED */
int
main (ac, av)
    int		ac;
    char	*av[];
{
  char buff[MAX_BUFF_LEN + 1];
  time_t d;

  (void)printf ("Enter date, or blank line to exit.\n\t> ");
  (void)fflush (stdout);

  buff[MAX_BUFF_LEN] = 0;
  while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) {
    d = get_date (buff, (struct timeb *)NULL);
    if (d == -1)
      (void)printf ("Bad format - couldn't convert.\n");
    else
      (void)printf ("%s", ctime (&d));
    (void)printf ("\t> ");
    (void)fflush (stdout);
  }
  exit (0);
  /* NOTREACHED */
}
#endif	/* defined (TEST) */
